import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;

/* TODO ClassLevelCommentBlock
 * 
 * @author Jake Parsons MUN#201030616.
 */

//Multi-threaded Producer/Consumer. Used to decouple two socket read/write threads and allow them to share their data
public class ConnectedDeviceBuffer {
	private final int BUF_MAX;
	private Deque<byte[]> packetBuf;
	
	private Semaphore bufMutex;
	private Semaphore bufFullCount;
	private Semaphore bufEmptyCount;
	
	
	public ConnectedDeviceBuffer(){
		BUF_MAX = 500;
		bufMutex = new Semaphore(1);
		bufFullCount = new Semaphore(0);
		bufEmptyCount = new Semaphore(BUF_MAX);
		
		packetBuf = new LinkedList<byte[]>();
	}

	public ConnectedDeviceBuffer(int maxBufSize){
		BUF_MAX = maxBufSize;
		bufMutex = new Semaphore(1);
		bufFullCount = new Semaphore(0);
		bufEmptyCount = new Semaphore(BUF_MAX);
		
		packetBuf = new LinkedList<byte[]>();
	}
	
	public boolean produce(byte[] packetToAdd){//put an item on the list
		//emptyCount--
		try {
			bufEmptyCount.acquire();
		} catch (InterruptedException e) {// TODO Auto-generated catch block
			e.printStackTrace();
			return false;//the push failed!
		}
		
		//mutex-- 
		//(be careful! if we bail here we need to release the emptycount we just acquired since we didn't push to the list
		try {
			bufMutex.acquire();
		} catch (InterruptedException e) {// TODO Auto-generated catch block
			bufEmptyCount.release();//if we bail here we need to release the emptycount we just acquired since we didn't push to the list
			e.printStackTrace();
			return false;//the push failed!
		}
		
		packetBuf.add(packetToAdd);//push
		bufMutex.release();//mutex++
		bufFullCount.release();//fullCount++
		return true;//the push worked!
	}
	
	public byte[] consume(){//remove an item from the list. return null if we couldn't get the thing
		//fullCount--
		try {
			bufFullCount.acquire();
		} catch (InterruptedException e) {// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
		
		//mutex--
		//(be careful! if we bail here we need to release the fullCount we just acquired since we didn't pop from the queue
		try {
			bufMutex.acquire();
		} catch (InterruptedException e) {// TODO Auto-generated catch block
			bufFullCount.release();//if we bail here we need to release the emptycount we just acquired since we didn't pop from the queue
			e.printStackTrace();
			return null;//the pop failed!
		}
		byte[] packetOutArr = packetBuf.pop();//pop
		bufMutex.release();//mutex++
		bufEmptyCount.release();//emptyCount++

		return packetOutArr;//the pop succeeded!
	}
	
	public byte[] tryConsume() { //similar to the above, but doesn't wait if the queue is empty
		if( bufFullCount.tryAcquire() ) { //produce releases bufFullCount last. if we got this there is something to consume.
			//(be careful! if we bail here we need to release the fullCount we just acquired since we didn't pop from the queue
			try {
				bufMutex.acquire();
			} catch (InterruptedException e) {// TODO Auto-generated catch block
				bufFullCount.release();//if we bail here we need to release the emptycount we just acquired since we didn't pop from the queue
				e.printStackTrace();
				return null;//the pop failed!
			}
			byte[] packetOutArr = packetBuf.pop();//pop
			bufMutex.release();//mutex++
			bufEmptyCount.release();//emptyCount++

			return packetOutArr;//the pop succeeded!
		} else {
			return null;
		}
	}
}
